/*
	Handles conversion of data into network byte order and vice versa.
*/

#include 	<stdio.h>
#include 	<stdlib.h>
#include 	<string.h>
#include 	<stdint.h>
#include 	<sys/types.h>
#include 	<sys/uio.h>
#include 	<unistd.h>
#include 	<arpa/inet.h>
#include 	<errno.h>
#include 	"types.h"
#include 	"protocol.h"
#include 	"player.h"
#include 	"player.c"
#include 	"net.h"
#include	"state.h"
#include	"update.c"


// global vars
Team Teams[NUMTEAMS];
int updateState;


/* Connect Body */
void sendConnectResponse(int sockfd, Maze *m, Player *myPlayer) {
	// create a player for this client
	// send pack the player number
	char data[CTF_HEADER_LENGTH + CONNECT_BODY_LENGTH +1];
	printf("connect response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( CONNECT );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( CONNECT_BODY_LENGTH );
	
	// player Id
	int playerId = myPlayer->id;
	printf("playerId %i\n", playerId);
	*( (short *) &data[12] ) = htons(playerId);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + CONNECT_BODY_LENGTH );
}

/* Move Body */
void sendMoveResponse(int sockfd, Maze *m, Position p, Player *myPlayerPtr) {
	// create a player for this client
	// send pack the player number
	char data[CTF_HEADER_LENGTH + MOVE_BODY_LENGTH +1];
	printf("move response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( MOVE );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( MOVE_BODY_LENGTH );
	
	// player Id
	*( (short *) &data[12] ) = htons(p.row);
	*( (short *) &data[14] ) = htons(p.col);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + MOVE_BODY_LENGTH );
	/*
	Player *thisPlayer;
	int i;
	for (i=0; i<NUMTEAMS; i++){
		Player *playerPtr = Teams[i].playerListPtr;
		do {
		
			if ( playerPtr->socketId == sockfd ) {
				//printf("\t\t playerId %i\n",);
				thisPlayer = playerPtr;
			}
			playerPtr = playerPtr->next;
		} while ( playerPtr != NULL);
	}
	*/
	// send move cell updates
	updateState++;
	printf("\t\t Team 1 %i, Team2 %i\n", Teams[0].numPlayers, Teams[1].numPlayers);
	int i;
	for (i=0; i<NUMTEAMS; i++){
		Player *playerPtr = Teams[i].playerListPtr;
		if ( playerPtr == NULL )
			continue;	
		do {
			// send to every client but yourself
			if ( playerPtr->id != myPlayerPtr->id ) {
				printf("\t\t playerId %i moved\n",playerPtr->id);
				int sock = playerPtr->socketId;
				sendCellUpdateMove(sock, p, myPlayerPtr->id);
			}
			playerPtr = playerPtr->next;
		} while ( playerPtr != NULL);
	}
  
	
}

/* Pickup Body */
void sendPickupResponse(int sockfd, Maze *m) {
	// create a player for this client
	// send pack the player number
	char data[CTF_HEADER_LENGTH + PICKUP_BODY_LENGTH +1];
	printf("pickup response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( PICKUP );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( PICKUP_BODY_LENGTH );
	
	// object Id
	int objectId = 2;
	*( (short *) &data[12] ) = htons(objectId);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + PICKUP_BODY_LENGTH );
}


/* Drop Body */
void sendDropResponse(int sockfd, Maze *m) {

	char data[CTF_HEADER_LENGTH + DROP_BODY_LENGTH +1];
	printf("drop response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( DROP );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( DROP_BODY_LENGTH );
	
	// object Id
	int objectId = 1;
	Position p = {41,89};
	*( (short *) &data[12] ) = htons(objectId);
	*( (short *) &data[14] ) = htons(p.row);
	*( (short *) &data[16] ) = htons(p.col);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + DROP_BODY_LENGTH );
}

/* Blast Body */
void sendBlastResponse(int sockfd, Maze *m) {

	char data[CTF_HEADER_LENGTH + BLAST_BODY_LENGTH +1];
	printf("blast response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( BLAST );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( BLAST_BODY_LENGTH );
	
	// blasted position
	Position p = {33,22};
	*( (short *) &data[12] ) = htons(p.row);
	*( (short *) &data[14] ) = htons(p.col);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + BLAST_BODY_LENGTH );
}

/* Disconnect Body */
void sendDisconnectResponse(int sockfd, Maze *m) {

	char data[CTF_HEADER_LENGTH + DISCONNECT_BODY_LENGTH +1];
	printf("disconnect response\n");
	// ctf packet header
	*( (short *) &data[0] ) = htons( 0 );
	*( (short *) &data[2] ) = htons( DISCONNECT );
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( DISCONNECT_BODY_LENGTH );
	
	// result
	int result = 1;
	*( (short *) &data[12] ) = htons(result);
	
	net_writen( sockfd, data, CTF_HEADER_LENGTH + DISCONNECT_BODY_LENGTH );
}


/* Map Transmission */
void sendMapPacket(int sockfd, Maze *m) {
	
	int length = CTF_HEADER_LENGTH + MAP_HEADER_LENGTH + (m->numCells);
	printf("length: %i\n", length);
	//char *data;
	//data = new char[length];
	char data[length + 1];
	printf("size of data %i\n", sizeof(data));

	// ctf packet header
	*( (short *) &data[0] ) = htons(0);
	*( (short *) &data[2] ) = htons(MAP);
	*(  (long *) &data[4] ) = htonl( updateState );	// NEEDS TO CHANGE
	*(  (long *) &data[8] ) = htonl( MAP_HEADER_LENGTH + (m->numCells) );
	
	/* map header */
	*( (short *) &data[12] ) = htons( m->numRows );
	*( (short *) &data[14] ) = htons( m->numCols );
	*( (short *) &data[16] ) = htons( m->numHome1 );
	*( (short *) &data[18] ) = htons( m->numJail1 );
	*( (short *) &data[20] ) = htons( m->numHome2 );
	*( (short *) &data[22] ) = htons( m->numJail2 );
	
	/* map body */	
	// get packet in chars
	char *mapBody = getMapChars(m, MAP_PATH);
	char *ptr = mapBody;
	int i;
	int bodyIndex = CTF_HEADER_LENGTH + MAP_HEADER_LENGTH; // start index in data array
	for(i=0; i<(m->numCells); i++) {
		*( (char *) &data[bodyIndex++] ) = (char) *ptr++;
	}

	// write to socket
	net_writen( sockfd, data, length );
}

/* receive and process incoming packet */
void receive( int sockfd, int port, Maze *m, Player *myPlayerPtr ) {
	printf("receive m=%i\n",m);
	char buf[5000];
	int headerSize;
	
	for( ; ; ){
	
	// retrieve header of size HEADER_SIZE_IN_BYTES
	if( ( headerSize = net_readn(sockfd, buf, CTF_HEADER_LENGTH)) < 0 ) 
		printf("Something's wrong, errno: %i\n", errno);
	
	// print header size to console. 
	printf("header size: %i\n", headerSize);

	CtfHeader header;

	// process received bytes
	char senderBytes[]		 =  { buf[0],  buf[1] };
	char msgTypeBytes[] 	 =  { buf[2],  buf[3] };
	char gameStateBytes[]	 =  { buf[4],  buf[5], buf[6], buf[7] };
	char bodyLengthBytes[] 	 =  { buf[8], buf[9], buf[10], buf[11] };
	
	// vars for network to host conversion
	uint16_t s = 0;
	uint16_t t = 0;
	uint64_t g = 0;
	uint64_t b = 0;
	
	// bytes to shorts
	memcpy( &s, senderBytes, 		sizeof(s) );
	memcpy( &t, msgTypeBytes, 		sizeof(t) );
	memcpy( &g, gameStateBytes, 	sizeof(g) );
	memcpy( &b, bodyLengthBytes, 	sizeof(b) );
	
	// assign to header 
	header.sender 		= ntohs( s );
	header.msgType 		= ntohs( t );
	header.gameState 	= ntohl( g );
	header.bodyLength 	= ntohl( b );;
	
	// print processed header to console
	printf("header: \n\t sender = %i \n\t msgType = %i \n\t gameState = %i \n\t bodyLength = %i\n", 
								header.sender, header.msgType, header.gameState, header.bodyLength);
	
	// clear out for message ( although shouldn't be any from client )
	bzero(&buf, sizeof(buf));
	
	Position movePos;
	// retrieve packet body, if exists  (Should only be on move)
	if ( header.bodyLength > 0 ) {
		bzero(&buf, sizeof(buf) );
		int msgSize = net_readn(sockfd, buf, header.bodyLength);
		
		if ( msgSize != header.bodyLength )
			printf("Message size (%i) is not expected body length (%i).\n", msgSize, header.bodyLength);
			
		if ( header.msgType == MOVE ) {
			// read in move position
			char rowBytes[]	=  { buf[0],  buf[1] };
			char colBytes[] =  { buf[2],  buf[3] };
			uint16_t r = 0;
			uint16_t c = 0;
			// bytes to shorts
			memcpy( &r, rowBytes, sizeof(r) );
			memcpy( &c, colBytes, sizeof(c) );
			movePos.row = ntohs(r);
			movePos.col = ntohs(c) ;
			/* PROCESS MOVE, pass on to sendMoveResponse? */
			
		printf("move request: (%i,%i)\n", movePos.row, movePos.col);
		}
		
		else if ( header.msgType == CHAT_MSG ) {
			char senderIdBytes[]	=  { buf[0],  buf[1] };
			char lengthBytes[] =  { buf[2],  buf[3] };
			
			char msgBytes[header.bodyLength-4];
			int i;
			for ( i = 4; i < header.bodyLength; i++ ) {
				msgBytes[i-4] = buf[i];
			}
			uint16_t r = 0;
			uint16_t c = 0;
			// bytes to shorts
			memcpy( &r,  senderIdBytes, sizeof(r) );
			memcpy( &c, lengthBytes, sizeof(c) );
			int senderId = ntohs(r);
			int msgLength = ntohs(c) ;
		
			broadcastChatMsg( msgBytes, msgLength, senderId );
		}
	}
	// end client msg data
	
	//printf("msg type %i\n", header.msgType);
	
	// send to client
	if ( header.msgType == ERROR) 
		exit(-1);	// to stop the infinite looping if client closes before server
	else if ( header.msgType == CONNECT) {
		// send map
		sendMapPacket(sockfd, m);
		// create player
		Player *myPlayerRet = player_add(m);
		// assign player to global var
		myPlayerPtr = myPlayerRet;
		myPlayerPtr->socketId = sockfd;
		// respond to client
		sendConnectResponse(sockfd, m, myPlayerRet);
		Position p = myPlayerPtr->cellPtr->pos;
		sendMoveResponse(sockfd, m, p, myPlayerPtr);
		
		
		// START GAME
		if ( numPlayers == NUM_PLAYERS_TO_START ) {
			
				if ( gameState == 0 ) {
					gameState = 4; // start game
					
					// send game start packet
					sendGameUpdateToAll(sockfd, gameState);
					
					// notify everyone of everyone else
					printf("send initial update %i\n", myPlayerPtr->id);
					sendInitialCellUpdates();
				}
		}
	}
	else if ( header.msgType == MOVE && gameState != 0 ) {
		Position p = maze_player_move(m,myPlayerPtr, movePos);
		sendMoveResponse(sockfd, m, p, myPlayerPtr);
	}
	else if ( header.msgType == PICKUP)
		sendPickupResponse(sockfd, m);
	else if ( header.msgType == DROP)
		sendDropResponse(sockfd, m);
	else if ( header.msgType == BLAST)
		sendBlastResponse(sockfd, m);
	else if ( header.msgType == DISCONNECT)
		sendDisconnectResponse(sockfd, m);
	else if ( header.msgType == CHAT_MSG)
		;// done above
	else if ( header.msgType == MAP)
		sendMapPacket(sockfd, m);
	
	else {
		// BELONGS AS METHODS OF UPDATE THREAD
		//sendCellUpdate(sockfd);
		//sendPlayerUpdate(sockfd);
		//sendGameUpdate(sockfd);
	}
		
	bzero(&buf, sizeof(buf) );
		
	} //end infinite loop
	
}
